﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

#nullable disable
namespace BeetleX.XRPC
{
    internal class ReflectionHandlerFactory
    {
        private static Dictionary<FieldInfo, GetValueHandler> mFieldGetHandlers = new Dictionary<FieldInfo, GetValueHandler>();
        private static Dictionary<FieldInfo, SetValueHandler> mFieldSetHandlers = new Dictionary<FieldInfo, SetValueHandler>();
        private static Dictionary<PropertyInfo, GetValueHandler> mPropertyGetHandlers = new Dictionary<PropertyInfo, GetValueHandler>();
        private static Dictionary<PropertyInfo, SetValueHandler> mPropertySetHandlers = new Dictionary<PropertyInfo, SetValueHandler>();
        private static Dictionary<MethodInfo, FastMethodHandler> mMethodHandlers = new Dictionary<MethodInfo, FastMethodHandler>();
        private static Dictionary<Type, ObjectInstanceHandler> mInstanceHandlers = new Dictionary<Type, ObjectInstanceHandler>();

        public static GetValueHandler FieldGetHandler(FieldInfo field)
        {
            GetValueHandler handler;
            if (ReflectionHandlerFactory.mFieldGetHandlers.ContainsKey(field))
            {
                handler = ReflectionHandlerFactory.mFieldGetHandlers[field];
            }
            else
            {
                lock (typeof(ReflectionHandlerFactory))
                {
                    if (ReflectionHandlerFactory.mFieldGetHandlers.ContainsKey(field))
                    {
                        handler = ReflectionHandlerFactory.mFieldGetHandlers[field];
                    }
                    else
                    {
                        handler = ReflectionHandlerFactory.CreateFieldGetHandler(field);
                        ReflectionHandlerFactory.mFieldGetHandlers.Add(field, handler);
                    }
                }
            }
            return handler;
        }

        private static GetValueHandler CreateFieldGetHandler(FieldInfo field)
        {
            DynamicMethod dynamicMethod = new DynamicMethod("", typeof(object), new Type[1]
            {
        typeof (object)
            }, field.DeclaringType);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldfld, field);
            ReflectionHandlerFactory.EmitBoxIfNeeded(ilGenerator, field.FieldType);
            ilGenerator.Emit(OpCodes.Ret);
            return (GetValueHandler)dynamicMethod.CreateDelegate(typeof(GetValueHandler));
        }

        public static SetValueHandler FieldSetHandler(FieldInfo field)
        {
            SetValueHandler setValueHandler;
            if (ReflectionHandlerFactory.mFieldSetHandlers.ContainsKey(field))
            {
                setValueHandler = ReflectionHandlerFactory.mFieldSetHandlers[field];
            }
            else
            {
                lock (typeof(ReflectionHandlerFactory))
                {
                    if (ReflectionHandlerFactory.mFieldSetHandlers.ContainsKey(field))
                    {
                        setValueHandler = ReflectionHandlerFactory.mFieldSetHandlers[field];
                    }
                    else
                    {
                        setValueHandler = ReflectionHandlerFactory.CreateFieldSetHandler(field);
                        ReflectionHandlerFactory.mFieldSetHandlers.Add(field, setValueHandler);
                    }
                }
            }
            return setValueHandler;
        }

        private static SetValueHandler CreateFieldSetHandler(FieldInfo field)
        {
            DynamicMethod dynamicMethod = new DynamicMethod("", (Type)null, new Type[2]
            {
        typeof (object),
        typeof (object)
            }, field.DeclaringType);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_1);
            ReflectionHandlerFactory.EmitCastToReference(ilGenerator, field.FieldType);
            ilGenerator.Emit(OpCodes.Stfld, field);
            ilGenerator.Emit(OpCodes.Ret);
            return (SetValueHandler)dynamicMethod.CreateDelegate(typeof(SetValueHandler));
        }

        public static SetValueHandler PropertySetHandler(PropertyInfo property)
        {
            SetValueHandler propertySetHandler;
            if (ReflectionHandlerFactory.mPropertySetHandlers.ContainsKey(property))
            {
                propertySetHandler = ReflectionHandlerFactory.mPropertySetHandlers[property];
            }
            else
            {
                lock (typeof(ReflectionHandlerFactory))
                {
                    if (ReflectionHandlerFactory.mPropertySetHandlers.ContainsKey(property))
                    {
                        propertySetHandler = ReflectionHandlerFactory.mPropertySetHandlers[property];
                    }
                    else
                    {
                        propertySetHandler = ReflectionHandlerFactory.CreatePropertySetHandler(property);
                        ReflectionHandlerFactory.mPropertySetHandlers.Add(property, propertySetHandler);
                    }
                }
            }
            return propertySetHandler;
        }

        private static SetValueHandler CreatePropertySetHandler(PropertyInfo property)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, (Type)null, new Type[2]
            {
        typeof (object),
        typeof (object)
            }, property.DeclaringType.Module);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_1);
            ReflectionHandlerFactory.EmitCastToReference(ilGenerator, property.PropertyType);
            ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), (Type[])null);
            ilGenerator.Emit(OpCodes.Ret);
            return (SetValueHandler)dynamicMethod.CreateDelegate(typeof(SetValueHandler));
        }

        public static GetValueHandler PropertyGetHandler(PropertyInfo property)
        {
            GetValueHandler propertyGetHandler;
            if (ReflectionHandlerFactory.mPropertyGetHandlers.ContainsKey(property))
            {
                propertyGetHandler = ReflectionHandlerFactory.mPropertyGetHandlers[property];
            }
            else
            {
                lock (typeof(ReflectionHandlerFactory))
                {
                    if (ReflectionHandlerFactory.mPropertyGetHandlers.ContainsKey(property))
                    {
                        propertyGetHandler = ReflectionHandlerFactory.mPropertyGetHandlers[property];
                    }
                    else
                    {
                        propertyGetHandler = ReflectionHandlerFactory.CreatePropertyGetHandler(property);
                        ReflectionHandlerFactory.mPropertyGetHandlers.Add(property, propertyGetHandler);
                    }
                }
            }
            return propertyGetHandler;
        }

        private static GetValueHandler CreatePropertyGetHandler(PropertyInfo property)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[1]
            {
        typeof (object)
            }, property.DeclaringType.Module);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), (Type[])null);
            ReflectionHandlerFactory.EmitBoxIfNeeded(ilGenerator, property.PropertyType);
            ilGenerator.Emit(OpCodes.Ret);
            return (GetValueHandler)dynamicMethod.CreateDelegate(typeof(GetValueHandler));
        }

        public static FastMethodHandler MethodHandler(MethodInfo method)
        {
            FastMethodHandler fastMethodHandler = (FastMethodHandler)null;
            if (ReflectionHandlerFactory.mMethodHandlers.ContainsKey(method))
            {
                fastMethodHandler = ReflectionHandlerFactory.mMethodHandlers[method];
            }
            else
            {
                lock (typeof(ReflectionHandlerFactory))
                {
                    if (ReflectionHandlerFactory.mMethodHandlers.ContainsKey(method))
                    {
                        fastMethodHandler = ReflectionHandlerFactory.mMethodHandlers[method];
                    }
                    else
                    {
                        fastMethodHandler = ReflectionHandlerFactory.CreateMethodHandler(method);
                        ReflectionHandlerFactory.mMethodHandlers.Add(method, fastMethodHandler);
                    }
                }
            }
            return fastMethodHandler;
        }

        private static FastMethodHandler CreateMethodHandler(MethodInfo methodInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[2]
            {
        typeof (object),
        typeof (object[])
            }, methodInfo.DeclaringType.Module);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ParameterInfo[] parameters = methodInfo.GetParameters();
            Type[] typeArray = new Type[parameters.Length];
            for (int index = 0; index < typeArray.Length; ++index)
                typeArray[index] = !parameters[index].ParameterType.IsByRef ? parameters[index].ParameterType : parameters[index].ParameterType.GetElementType();
            LocalBuilder[] localBuilderArray = new LocalBuilder[typeArray.Length];
            for (int index = 0; index < typeArray.Length; ++index)
                localBuilderArray[index] = ilGenerator.DeclareLocal(typeArray[index], true);
            for (int index = 0; index < typeArray.Length; ++index)
            {
                ilGenerator.Emit(OpCodes.Ldarg_1);
                ReflectionHandlerFactory.EmitFastInt(ilGenerator, index);
                ilGenerator.Emit(OpCodes.Ldelem_Ref);
                ReflectionHandlerFactory.EmitCastToReference(ilGenerator, typeArray[index]);
                ilGenerator.Emit(OpCodes.Stloc, localBuilderArray[index]);
            }
            if (!methodInfo.IsStatic)
                ilGenerator.Emit(OpCodes.Ldarg_0);
            for (int index = 0; index < typeArray.Length; ++index)
            {
                if (parameters[index].ParameterType.IsByRef)
                    ilGenerator.Emit(OpCodes.Ldloca_S, localBuilderArray[index]);
                else
                    ilGenerator.Emit(OpCodes.Ldloc, localBuilderArray[index]);
            }
            if (methodInfo.IsStatic)
                ilGenerator.EmitCall(OpCodes.Call, methodInfo, (Type[])null);
            else
                ilGenerator.EmitCall(OpCodes.Callvirt, methodInfo, (Type[])null);
            if (methodInfo.ReturnType == typeof(void))
                ilGenerator.Emit(OpCodes.Ldnull);
            else
                ReflectionHandlerFactory.EmitBoxIfNeeded(ilGenerator, methodInfo.ReturnType);
            for (int index = 0; index < typeArray.Length; ++index)
            {
                if (parameters[index].ParameterType.IsByRef)
                {
                    ilGenerator.Emit(OpCodes.Ldarg_1);
                    ReflectionHandlerFactory.EmitFastInt(ilGenerator, index);
                    ilGenerator.Emit(OpCodes.Ldloc, localBuilderArray[index]);
                    if (localBuilderArray[index].LocalType.IsValueType)
                        ilGenerator.Emit(OpCodes.Box, localBuilderArray[index].LocalType);
                    ilGenerator.Emit(OpCodes.Stelem_Ref);
                }
            }
            ilGenerator.Emit(OpCodes.Ret);
            return (FastMethodHandler)dynamicMethod.CreateDelegate(typeof(FastMethodHandler));
        }

        public static ObjectInstanceHandler InstanceHandler(Type type)
        {
            ObjectInstanceHandler objectInstanceHandler;
            if (ReflectionHandlerFactory.mInstanceHandlers.ContainsKey(type))
            {
                objectInstanceHandler = ReflectionHandlerFactory.mInstanceHandlers[type];
            }
            else
            {
                lock (typeof(ReflectionHandlerFactory))
                {
                    if (ReflectionHandlerFactory.mInstanceHandlers.ContainsKey(type))
                    {
                        objectInstanceHandler = ReflectionHandlerFactory.mInstanceHandlers[type];
                    }
                    else
                    {
                        objectInstanceHandler = ReflectionHandlerFactory.CreateInstanceHandler(type);
                        ReflectionHandlerFactory.mInstanceHandlers.Add(type, objectInstanceHandler);
                    }
                }
            }
            return objectInstanceHandler;
        }

        private static ObjectInstanceHandler CreateInstanceHandler(Type type)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, type, (Type[])null, type.Module);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.DeclareLocal(type, true);
            ilGenerator.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0]));
            ilGenerator.Emit(OpCodes.Stloc_0);
            ilGenerator.Emit(OpCodes.Ldloc_0);
            ilGenerator.Emit(OpCodes.Ret);
            return (ObjectInstanceHandler)dynamicMethod.CreateDelegate(typeof(ObjectInstanceHandler));
        }

        private static void EmitCastToReference(ILGenerator il, Type type)
        {
            if (type.IsValueType)
                il.Emit(OpCodes.Unbox_Any, type);
            else
                il.Emit(OpCodes.Castclass, type);
        }

        private static void EmitBoxIfNeeded(ILGenerator il, Type type)
        {
            if (!type.IsValueType)
                return;
            il.Emit(OpCodes.Box, type);
        }

        private static void EmitFastInt(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    break;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    break;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    break;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    break;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    break;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    break;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    break;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    break;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    break;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    break;
                default:
                    if (value > -129 && value < 128)
                    {
                        il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
                        break;
                    }
                    il.Emit(OpCodes.Ldc_I4, value);
                    break;
            }
        }
    }
}
